#region
//
// Bdev.Net.Dns by Rob Philpott, Big Developments Ltd. Please send all bugs/enhancements to
// rob@bigdevelopments.co.uk  This file and the code contained within is freeware and may be
// distributed and edited without restriction.
// 

#endregion

using System;
using System.Text;

namespace Bdev.Net.Dns
{
    /// <summary>
    /// Logical representation of a pointer, but in fact a byte array reference and a position in it. This
    /// is used to read logical units (bytes, shorts, integers, domain names etc.) from a byte array, keeping
    /// the pointer updated and pointing to the next record. This type of Pointer can be considered the logical
    /// equivalent of an (unsigned char*) in C++
    /// </summary>
    internal class Pointer
    {
        // a pointer is a reference to the message and an index
        private byte[] _message;
        private int _position;

        // pointers can only be created by passing on an existing message
        public Pointer(byte[] message, int position)
        {
            _message = message;
            _position = position;
        }

        /// <summary>
        /// Shallow copy function
        /// </summary>
        /// <returns></returns>
        public Pointer Copy()
        {
            return new Pointer(_message, _position);
        }

        /// <summary>
        /// Adjust the pointers position within the message
        /// </summary>
        /// <param name="position">new position in the message</param>
        public void SetPosition(int position)
        {
            _position = position;
        }

        /// <summary>
        /// Overloads the + operator to allow advancing the pointer by so many bytes
        /// </summary>
        /// <param name="pointer">the initial pointer</param>
        /// <param name="offset">the offset to add to the pointer in bytes</param>
        /// <returns>a reference to a new pointer moved forward by offset bytes</returns>
        public static Pointer operator +(Pointer pointer, int offset)
        {
            return new Pointer(pointer._message, pointer._position + offset);
        }

        /// <summary>
        /// Reads a single byte at the current pointer, does not advance pointer
        /// </summary>
        /// <returns>the byte at the pointer</returns>
        public byte Peek()
        {
            return _message[_position];
        }

        /// <summary>
        /// Reads a single byte at the current pointer, advancing pointer
        /// </summary>
        /// <returns>the byte at the pointer</returns>
        public byte ReadByte()
        {
            return _message[_position++];
        }

        /// <summary>
        /// Reads two bytes to form a short at the current pointer, advancing pointer
        /// </summary>
        /// <returns>the byte at the pointer</returns>
        public short ReadShort()
        {
            return (short)(ReadByte() << 8 | ReadByte());
        }

        /// <summary>
        /// Reads four bytes to form a int at the current pointer, advancing pointer
        /// </summary>
        /// <returns>the byte at the pointer</returns>
        public int ReadInt()
        {
            return (ushort)ReadShort() << 16 | (ushort)ReadShort();
        }

        /// <summary>
        /// Reads a single byte as a char at the current pointer, advancing pointer
        /// </summary>
        /// <returns>the byte at the pointer</returns>
        public char ReadChar()
        {
            return (char)ReadByte();
        }

        /// <summary>
        /// Reads a domain name from the byte array. The method by which this works is described
        /// in RFC1035 - 4.1.4. Essentially to minimise the size of the message, if part of a domain
        /// name already been seen in the message, rather than repeating it, a pointer to the existing
        /// definition is used. Each word in a domain name is a label, and is preceded by its length
        /// 
        /// eg. bigdevelopments.co.uk
        /// 
        /// is [15] (size of bigdevelopments) + "bigdevelopments"
        ///    [2]  "co"
        ///    [2]  "uk"
        ///    [1]  0 (NULL)
        /// </summary>
        /// <returns>the byte at the pointer</returns>
        public string ReadDomain()
        {
            StringBuilder domain = new StringBuilder();
            int length = 0;

            // get  the length of the first label
            while ((length = ReadByte()) != 0)
            {
                // top 2 bits set denotes domain name compression and to reference elsewhere
                if ((length & 0xc0) == 0xc0)
                {
                    // work out the existing domain name, copy this pointer
                    Pointer newPointer = Copy();

                    // and move it to where specified here
                    newPointer.SetPosition((length & 0x3f) << 8 | ReadByte());

                    // repeat call recursively
                    domain.Append(newPointer.ReadDomain());
                    return domain.ToString();
                }

                // if not using compression, copy a char at a time to the domain name
                while (length > 0)
                {
                    domain.Append(ReadChar());
                    length--;
                }

                // if size of next label isn't null (end of domain name) add a period ready for next label
                if (Peek() != 0) domain.Append('.');
            }

            // and return
            return domain.ToString();
        }
    }
}